﻿// -----------------------------------------------------------------------
// Copyright (c) Microsoft Corporation.  All rights reserved.
// -----------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Microsoft.Internal;
using Microsoft.Internal.Collections;

namespace System.ComponentModel.Composition
{
    /// <summary>
    ///     Defines the <see langword="abstract"/> base class for export providers, which provide
    ///     methods for retrieving <see cref="Export"/> objects.
    /// </summary>
    public abstract partial class ExportProvider
    {
        /// <summary>
        ///     Initializes a new instance of the <see cref="ExportProvider"/> class.
        /// </summary>
        protected ExportProvider()
        {
        }

        /// <summary>
        ///     Occurs when the exports in the <see cref="ExportProvider"/> change.
        /// </summary>
        public virtual event EventHandler<ExportsChangedEventArgs> ExportsChanged;

        /// <summary>
        ///     Raises the <see cref="ExportsChanged"/> event.
        /// </summary>
        /// <param name="e">
        ///     An <see cref="ExportsChangedEventArgs"/> containing the data for the event.
        /// </param>
        protected virtual void OnExportsChanged(ExportsChangedEventArgs e)
        {
            if (this.ExportsChanged != null)
            {
                this.ExportsChanged(this, e);
            }
        }

        /// <summary>
        ///     Returns all exports that match the conditions of the specified import.
        /// </summary>
        /// <param name="importDefinition">
        ///     The <see cref="ImportDefinition"/> that defines the conditions of the 
        ///     <see cref="Export"/> objects to get.
        /// </param>
        /// <result>
        ///     An <see cref="IEnumerable{T}"/> of <see cref="Export"/> objects that match 
        ///     the conditions defined by <see cref="ImportDefinition"/>, if found; otherwise, an 
        ///     empty <see cref="IEnumerable{T}"/>.
        /// </result>
        /// <remarks>
        ///     <note type="inheritinfo">
        ///         Overriders of this method should not treat cardinality-related mismatches 
        ///         as errors, and should not throw exceptions in those cases. For instance,
        ///         if <see cref="ImportDefinition.Cardinality"/> is <see cref="ImportCardinality.ExactlyOne"/> 
        ///         and there are zero <see cref="Export"/> objects that match the conditions of the 
        ///         specified <see cref="ImportDefinition"/>, an <see cref="IEnumerable{T}"/> should be returned.
        ///     </note>
        /// </remarks>
        protected abstract IEnumerable<Export> GetExportsCore(ImportDefinition importDefinition);

        /// <summary>
        ///     Returns all exports that match the conditions of the specified import.
        /// </summary>
        /// <param name="importDefinition">
        ///     The <see cref="ImportDefinition"/> that defines the conditions of the 
        ///     <see cref="Export"/> objects to get.
        /// </param>
        /// <result>
        ///     An <see cref="IEnumerable{T}"/> of <see cref="Export"/> objects that match 
        ///     the conditions defined by <see cref="ImportDefinition"/>, if found; otherwise, an 
        ///     empty <see cref="IEnumerable{T}"/>.
        /// </result>
        /// <exception cref="ArgumentNullException">
        ///     <paramref name="importDefinition"/> is <see langword="null"/>.
        /// </exception>
        /// <exception cref="CardinalityMismatchException">
        ///     <para>
        ///         <see cref="ImportDefinition.Cardinality"/> is <see cref="ImportCardinality.ExactlyOne"/> and 
        ///         there are zero <see cref="Export"/> objects that match the conditions of the specified 
        ///         <see cref="ImportDefinition"/>.
        ///     </para>
        ///     -or-
        ///     <para>
        ///         <see cref="ImportDefinition.Cardinality"/> is <see cref="ImportCardinality.ZeroOrOne"/> or 
        ///         <see cref="ImportCardinality.ExactlyOne"/> and there are more than one <see cref="Export"/> 
        ///         objects that match the conditions of the specified <see cref="ImportDefinition"/>.
        ///     </para>
        /// </exception>
        public IEnumerable<Export> GetExports(ImportDefinition importDefinition)
        {
            Requires.NotNull(importDefinition, "importDefinition");

            IEnumerable<Export> exports = null;
            ExportCardinalityCheckResult result = this.TryGetExportsCore(importDefinition, out exports);
            switch(result)
            {
                case ExportCardinalityCheckResult.Match:
                    return exports;
                case ExportCardinalityCheckResult.NoExports:
                    throw new CardinalityMismatchException(string.Format(CultureInfo.CurrentCulture, Strings.CardinalityMismatch_NoExports, importDefinition.Constraint.Body.ToString()));
                default:
                    Assumes.IsTrue(result == ExportCardinalityCheckResult.TooManyExports);
                    throw new CardinalityMismatchException(string.Format(CultureInfo.CurrentCulture, Strings.CardinalityMismatch_TooManyExports, importDefinition.Constraint.Body.ToString()));
            }
        }

        /// <summary>
        ///     Returns all exports that match the conditions of the specified import.
        /// </summary>
        /// <param name="importDefinition">
        ///     The <see cref="ImportDefinition"/> that defines the conditions of the 
        ///     <see cref="Export"/> objects to get.
        /// </param>
        /// <param name="exports">
        ///     When this method returns, contains an <see cref="IEnumerable{T}"/> of <see cref="Export"/> 
        ///     objects that match the conditions defined by <see cref="ImportDefinition"/>, if found; 
        ///     otherwise, an empty <see cref="IEnumerable{T}"/>.
        /// </param>
        /// <returns>
        ///     <see langword="true"/> if <see cref="ImportDefinition.Cardinality"/> is 
        ///     <see cref="ImportCardinality.ZeroOrOne"/> or <see cref="ImportCardinality.ZeroOrMore"/> and 
        ///     there are zero <see cref="Export"/> objects that match the conditions of the specified 
        ///     <see cref="ImportDefinition"/>. <see langword="true"/> if 
        ///     <see cref="ImportDefinition.Cardinality"/> is <see cref="ImportCardinality.ZeroOrOne"/> or 
        ///     <see cref="ImportCardinality.ExactlyOne"/> and there is exactly one <see cref="Export"/> 
        ///     that matches the conditions of the specified <see cref="ImportDefinition"/>; otherwise, 
        ///     <see langword="false"/>.
        /// </returns>
        /// <exception cref="ArgumentNullException">
        ///     <paramref name="importDefinition"/> is <see langword="null"/>.
        /// </exception>
        public bool TryGetExports(ImportDefinition importDefinition, out IEnumerable<Export> exports)
        {
            Requires.NotNull(importDefinition, "importDefinition");

            exports = null;
            ExportCardinalityCheckResult result = this.TryGetExportsCore(importDefinition, out exports);
            return (result == ExportCardinalityCheckResult.Match);
        }

        private ExportCardinalityCheckResult TryGetExportsCore(ImportDefinition importDefinition, out IEnumerable<Export> exports)
        {
            Assumes.NotNull(importDefinition);

            exports = this.GetExportsCore(importDefinition);
            if (exports == null)
            {
                exports = Enumerable.Empty<Export>();
            }

            exports = exports.WhereNotNull();

            return ExportProvider.CheckCardinality(exports, importDefinition);
        }

        private static ExportCardinalityCheckResult CheckCardinality(IEnumerable<Export> exports, ImportDefinition importDefinition)
        {
            Assumes.NotNull(exports);
            Assumes.NotNull(importDefinition);

            EnumerableCardinality actualCardinality = exports.GetCardinality();

            switch (actualCardinality)
            {
                case EnumerableCardinality.Zero:
                    if (importDefinition.Cardinality == ImportCardinality.ExactlyOne)
                    {
                        return ExportCardinalityCheckResult.NoExports;
                    }
                    break;

                case EnumerableCardinality.TwoOrMore:
                    if (importDefinition.Cardinality.IsAtMostOne())
                    {
                        return ExportCardinalityCheckResult.TooManyExports;
                    }
                    break;

                default:
                    Assumes.IsTrue(actualCardinality == EnumerableCardinality.One);
                    break;

            }
            return ExportCardinalityCheckResult.Match;
        }
    }
}
